package pl.edu.mimuw.irs.core.model.analysis;

import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.LinkedHashMap;
import java.util.LinkedHashSet;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.Map.Entry;

import pl.edu.mimuw.irs.core.model.analysis.dd.ActivityWrapper;
import pl.edu.mimuw.irs.core.model.analysis.dd.DDFire;
import pl.edu.mimuw.irs.core.model.analysis.dd.DDHire;
import pl.edu.mimuw.irs.core.model.analysis.dd.DependenciesGraph;
import pl.edu.mimuw.irs.core.model.design.processing.Resource;
import pl.edu.mimuw.irs.core.model.petrinet.Transition;
import pl.edu.mimuw.irs.rcp.util.Pair;

public class History {

	// *** Attributes

	private List<Transition> fullList;
	private LinkedHashMap<ActivityWrapper, Set<ResourcePlace>> busyActivities;
	private Map<ActivityWrapper, Time> startTimes;
	/*
	 * Chronology is created only for activities 
	 * (only activities supports timestamps). XXX : add timestamps to all
	 * transitions.
	 */
	private Map<Time, Map<Transition, Set<ResourcePlace>>> chronology;
	private Time timestamp;
	private DependenciesGraph graph;
	private Map<Resource, Time> usage;
	private Map<Resource, Time> workTime;
	public List<Transition> getFullList() {
		if (fullList == null) fullList = new LinkedList<Transition>();
		return fullList;
	}
	public Map<ActivityWrapper, Set<ResourcePlace>> getBusyActivities() {
		if (busyActivities == null) busyActivities =
			new LinkedHashMap<ActivityWrapper, Set<ResourcePlace>>();
		return busyActivities;
	}
	public Map<ActivityWrapper, Time> getStartTimes() {
		if (startTimes == null) startTimes = 
			new LinkedHashMap<ActivityWrapper, Time>();
		return startTimes;
	}
	public Map<Time, Map<Transition, Set<ResourcePlace>>> getChronology() {
		if (chronology == null) chronology = 
			new LinkedHashMap<Time, Map<Transition, Set<ResourcePlace>>>();
		return chronology;
	}
	public Time getTimestamp() {
		return timestamp;
	}
	public DependenciesGraph getGraph() {
		return graph;
	}
	public Map<Resource, Time> getUsage() {
		if (usage == null) usage = new LinkedHashMap<Resource, Time>();
		return usage;
	}
	public void addUsage(Resource resource, Time time) {
		Time usage = getUsage().get(resource);
		if (usage == null) {
			usage = new Time();
			getUsage().put(resource, usage);
		}
		usage.add(time);
	}
	public Map<Resource, Time> getWorkTime() {
		if (workTime == null) workTime = new LinkedHashMap<Resource, Time>();
		return workTime;
	}
	public void addWorkTime(Resource resource, int i) {
		Time workTime = getWorkTime().get(resource);
		if (workTime == null) {
			workTime = new Time();
			getWorkTime().put(resource, workTime);
		}
		workTime.add(i);
	}

	// *** Constructors

	public History(DependenciesGraph graph) {
		this.graph = graph;
	}
	
	// *** Methods

	public void addInvoke(Transition transition) {
		if (transition instanceof DDHire) {
			/*
			 * Getting and preparing parameters.
			 */
			DDHire hire = (DDHire) transition;
			ActivityWrapper activity = hire.getActivity();
			Set<ResourcePlace> hired = hire.getHiredResources().get(activity.getActivity());
			/* 
			 * If hire has not been performed because of lack of resources,
			 * we skip this step in the history.
			 */
			if (hired == null) return;
			Set<ResourcePlace> hiredCopy = new LinkedHashSet<ResourcePlace>(hired);
			Time timestamp = activity.getTimestamp().copy();
			/*
			 * Storing data.
			 */
			getBusyActivities().put(activity, hiredCopy);
			getStartTimes().put(activity, timestamp);
			addEvent(timestamp, hire, hiredCopy);
		} else if (transition instanceof DDFire) {
			/*
			 * Getting and preparing parameters.
			 */
			DDFire fire = (DDFire) transition;
			ActivityWrapper activity = fire.getActivity();
			Time timestamp = activity.getTimestamp().copy();
			Set<ResourcePlace> fired = getBusyActivities().get(activity);
			getBusyActivities().remove(activity);
			Time start = getStartTimes().get(activity);
			getStartTimes().remove(activity);
			/*
			 * Storing data.
			 */
			for (ResourcePlace resourcePlace : fired) {
				addUsage(resourcePlace.getResource(), new Time(start, timestamp));
				addWorkTime(resourcePlace.getResource(), activity.getActivity().getTimeUnits());
			}
			addEvent(timestamp, fire, fired);
		}
		getFullList().add(transition);
	}
	
	private void addEvent(Time timestamp, Transition event, Set<ResourcePlace> hiredCopy) {
		Map<Transition, Set<ResourcePlace>> events = getChronology().get(timestamp);
		if (events == null) {
			events = new LinkedHashMap<Transition, Set<ResourcePlace>>();
			getChronology().put(timestamp, events);
		}
		events.put(event, hiredCopy);
	}

	public int getTotalUsageCost() {
		int total = 0;
		for (Entry<Resource, Time> entry : getUsage().entrySet()) {
			total += entry.getKey().getUnitCost() * entry.getValue().getValue();
		}
		return total;
	}
	
	public List<Entry<Resource, Time>> getBusiest() {
		List<Entry<Resource, Time>> usage = 
			new ArrayList<Entry<Resource, Time>>(getUsage().entrySet());
		Collections.sort(usage, new BusiestComparator());
		return usage;
	}
	
	public List<Pair<Resource, Integer>> getMostExpensive() {
		List<Pair<Resource, Integer>> costs = 
			new ArrayList<Pair<Resource, Integer>>();
		for (Entry<Resource, Time> entry : getUsage().entrySet()) {
			costs.add(new Pair<Resource, Integer>(entry.getKey(), 
					entry.getValue().getValue() * entry.getKey().getUnitCost()));
		}
		Collections.sort(costs, new MostExpensiveComparator());
		return costs;
	}
	
	public void close() {
		this.timestamp = getGraph().getTimestamp();
	}
	
	
	@Override
	public String toString() {
		return "History(" + getFullList() + ")";
	}
	
	public class BusiestComparator implements Comparator<Entry<Resource, Time>> {

		@Override
		public int compare(Entry<Resource, Time> arg0,
				Entry<Resource, Time> arg1) {
			return arg0.getValue().getValue() - arg1.getValue().getValue();
		}
	}

	public class MostExpensiveComparator implements Comparator<Pair<Resource, Integer>> {
		@Override
		public int compare(Pair<Resource, Integer> arg0,
				Pair<Resource, Integer> arg1) {
			return arg1.getValue() - arg0.getValue();
		}
	}
}
